﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using UnityEngine;

namespace Runtime.Common.Utility
{
    public class AssemblyInfo 
    {
        private Dictionary<string, Assembly> assemblyDict;
        private Assembly[] assemblyList;

        public AssemblyInfo(Assembly[] assemblyList)
        {
            this.assemblyList = assemblyList;
            Init();
        }

        public List<Type> AllTypeList { get; private set; }

        public void Destroy()
        {
            assemblyList = null;
            assemblyDict = null;
            AllTypeList = null;
        }

        private void Init()
        {
            assemblyDict = new Dictionary<string, Assembly>();
            AllTypeList = new List<Type>();
            InitDict();
        }

        private void InitDict()
        {
            foreach (var item in assemblyList)
            {
                var name = item.GetName().Name;
                if (assemblyDict.ContainsKey(name)) Debug.LogError($"存在相同类名{name}");

                assemblyDict[name] = item;
                AllTypeList.AddRange(item.GetTypes());
            }
        }

        public List<Type> GetTypesByInheritObjectType(string inheritClassName)
        {
            return AllTypeList.FindAll(t => t.GetInterface(inheritClassName) != null);
        }

        public List<Type> GetTypesByInheritClassName(string inheritClassName)
        {
            return AllTypeList.FindAll(t => t.FindInterfaces(MyInterfaceFilter, inheritClassName).Length > 0);
        }

        private bool MyInterfaceFilter(Type typeObj, object criteriaObj)
        {
            return typeObj.ToString() == criteriaObj.ToString();
        }

        public Assembly GetAssembly(string assemblyName)
        {
            assemblyDict.TryGetValue(assemblyName, out var assembly);
            return assembly;
        }

        public List<Assembly> GetAssemblies(string[] assemblyNames)
        {
            var assemblies = new List<Assembly>();
            foreach (var name in assemblyNames)
                if (assemblyDict.TryGetValue(name, out var assembly))
                    assemblies.Add(assembly);

            return assemblies;
        }

        public Type[] GetTypesByAssemblies(string[] assemblyNames, string typeName)
        {
            var types = new List<Type>();
            foreach (var name in assemblyNames)
            {
                var assembly = GetAssembly(name);
                if (assembly != null) types.AddRange(GetTypesByAssembly(assembly, typeName));
            }

            return types.ToArray();
        }

        public Type[] GetTypesByAssemblies(string typeName)
        {
            var types = new List<Type>();
            foreach (var assembly in assemblyList) types.AddRange(GetTypesByAssembly(assembly, typeName));
            return types.ToArray();
        }

        private IEnumerable<Type> GetTypesByAssembly(Assembly assembly, string typeName)
        {
            return assembly.GetModules()
                .SelectMany(module => module.FindTypes((t, obj) => t.Name == typeName, null));
        }
    }

    public static class AssemblyEx
    {
        private static readonly AssemblyInfo assemblyInfoAll;
        private static readonly AssemblyInfo assemblyInfoCustom;

        static AssemblyEx()
        {
            var assemblyList = AppDomain.CurrentDomain.GetAssemblies();
            assemblyInfoAll = new AssemblyInfo(assemblyList);
            var customAssemblies = assemblyList.Where(a => a.GetName().Name.StartsWith("Runtime")).ToArray();
            assemblyInfoCustom = new AssemblyInfo(customAssemblies);
        }

        public static AssemblyInfo GetAssemblyInfo(bool isCustom = true)
        {
            return isCustom ? assemblyInfoCustom : assemblyInfoAll;
        }

        public static Assembly GetAssembly(string assemblyName, bool isCustom = true)
        {
            return GetAssemblyInfo(isCustom)?.GetAssembly(assemblyName);
        }

        public static Type[] GetTypesByAssemblies(string[] assemblyNames, string typeName, bool isCustom = true)
        {
            return GetAssemblyInfo(isCustom)?.GetTypesByAssemblies(assemblyNames, typeName);
        }

        public static Type[] GetTypesByAssemblies(string typeName, bool isCustom = true)
        {
            return GetAssemblyInfo(isCustom)?.GetTypesByAssemblies(typeName);
        }

        public static List<Type> GetTypesByInheritObjectType<T>(bool isCustom = true)
        {
            return GetAssemblyInfo(isCustom)?.GetTypesByInheritObjectType(typeof(T).FullName);
        }

        public static List<Type> GetAllTypes(bool isCustom = true)
        {
            return GetAssemblyInfo(isCustom)?.AllTypeList;
        }

        //[StaticObjectRestoreMothed]
        public static void Destroy()
        {
            assemblyInfoAll.Destroy();
            assemblyInfoCustom.Destroy();
        }
    }
}